/** @file material.cpp
	@brief Material */

#include "material.h"

#include <GL/gl.h>

using namespace G3;

//==============================================================================
// The default clay material
//==============================================================================
G3::MATERIAL G3::DefaultMaterial;

//==============================================================================
// Global variables for the materialscripts
//==============================================================================
string G3MS_Name;
COL4F G3MS_Ambient;
COL4F G3MS_Diffuse;
COL4F G3MS_Specular;
COL4F G3MS_Emission;
float G3MS_Shininess;
float G3MS_Reflectance;
std::vector<G3MS_Texture> G3MS_Textures;
G3::SHADER_PROGRAM G3MS_ShaderProgram;

//==============================================================================
// Constructors
//==============================================================================
G3::MATERIAL::MATERIAL () {
    name = "Clay";

    log = &MainLog;

	// Default: Clay
	ambient = COL4F (0.01f, 0.01f, 0.01f, 1.0f);
	diffuse = COL4F (1.0f, 1.0f, 1.0f, 1.0f);
	specular = COL4F (0.1f, 0.1f, 0.1f, 1.0f);

	shininess = 0.0f;
	reflectance = 0.1f;
	emission = 0.0f;
}

G3::MATERIAL::MATERIAL (const MATERIAL &mat) {
    name = mat.name;

	ambient = mat.ambient;
	diffuse = mat.diffuse;
	specular = mat.specular;

	shininess = mat.shininess;
	reflectance = mat.reflectance;
	emission = mat.emission;

	textures = mat.textures;

	shader_program = mat.shader_program;
}

//==============================================================================
// = operators
//==============================================================================
MATERIAL& MATERIAL::operator=(const MATERIAL &m) {
    name = m.name;

	ambient = m.ambient;
	diffuse = m.diffuse;
	specular = m.specular;

	shininess = m.shininess;
	reflectance = m.reflectance;
	emission = m.emission;

	textures = m.textures;

	shader_program = m.shader_program;

	return *this;
}

//==============================================================================
// Loads a material script from a stream
//==============================================================================
bool G3::MATERIAL::Load (STREAM *aStream) {
    //! \todo Should support loading plain texture files aswell
	bool Result = false;
	TEXTURE_FILTER LastFilter = TextureManager.used_filter;

	Assert (aStream, "MATERIAL::Load: Stream argument is a NULL pointer");

	tolua_materialscript_open (MainLuaHandler.LuaState);

	if (MainLuaHandler.Execute (aStream) == -1) {
	    if (log)
            log->Report ("MATERIAL::Load: Failed to execute the script..\n");
		return false;
	}

    name = G3MS_Name;
	ambient = G3MS_Ambient;
	diffuse = G3MS_Diffuse;
	specular = G3MS_Specular;
	emission = G3MS_Emission;
	shininess = G3MS_Shininess;
	reflectance = G3MS_Reflectance;

	for (int i=0; i<(int) G3MS_Textures.size (); i++) {
		TextureManager.used_filter = G3MS_Textures [i].Filter;

        if (!G3MS_Textures [i].Filename.empty ())
            textures.push_back (TextureManager.GetHandle (G3MS_Textures [i].Filename));
	}

	TextureManager.used_filter = LastFilter;

	shader_program = G3MS_ShaderProgram;
	shader_program.Link ();

    G3MS_Textures.clear ();
    G3MS_ShaderProgram.Clear ();

	return Result;
}

//==============================================================================
// Loads a material script from a file
//==============================================================================
bool G3::MATERIAL::Load (const char *aFilename) {
    if (!aFilename) {
        if (log)
            log->Report ("MATERIAL::Load: Filename argument is a NULL pointer\n");
        return false;
    }

    if (log)
        log->Report ("Loading materialscript \"%s\"..\n", aFilename);

	string Path = MainFilesys.GetPath (aFilename);

    if (Path.empty () || !MainFileHandler.FileExists (Path))
        return false;

    STREAM *stream = MainFileHandler.ReadWholeFile (Path);

    if (!stream) {
        if (log)
            log->Report ("Failed to load the materialscript..\n");
        return false;
    }

	// Materialscripts are small - no need to stream them
	return Load (stream);
}

//==============================================================================
// Applies the material to OpenGL renderer
//==============================================================================
void G3::MATERIAL::glApply () {
	 int i = 0;

     glMaterialf (GL_FRONT, GL_SHININESS, shininess);

	 glColorMaterial (GL_FRONT, GL_AMBIENT);
	 glColor4f (ambient.r, ambient.g, ambient.b, ambient.a);

	 glColorMaterial (GL_FRONT, GL_SPECULAR);
	 glColor4f (specular.r, specular.g, specular.b, specular.a);

	 glColorMaterial (GL_FRONT, GL_EMISSION);
	 glColor4f (emission.r, emission.g, emission.b, emission.a);

	 // This should be the last one - user should be able to specify
	 // the diffuse color with the glColor functions
	 glColorMaterial (GL_FRONT, GL_DIFFUSE);
	 glColor4f (diffuse.r, diffuse.g, diffuse.b, diffuse.a);

	 glEnable (GL_COLOR_MATERIAL);

	 if (!textures.empty ()) {
	 	//! \todo Apply all textures with the corresponding blendfactors, mapping flags and so on..
		glEnable (GL_TEXTURE_2D);

	 	for (i=0; i<(int) textures.size (); i++) {
            if (textures [i].IsNull ())
                continue;

			if (TextureManager.GetTextureID (textures [i]) >= 0) {
				// Simple Alpha blending
				if (TextureManager.GetTextureFlags (textures [i]) & BITMAP_RGBA) {
					glEnable (GL_BLEND);
					glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				} else
					glDisable (GL_BLEND);

				glBindTexture (GL_TEXTURE_2D, (uint) TextureManager.GetTextureID (textures [i]));
			}
	 	}
	 } else {
		 glDisable (GL_TEXTURE_2D);
	 }

	 //! \todo Runtime Lua material scripts
	 shader_program.Apply ();
}

void G3::MATERIAL::glUnApply () {
    //! \todo UnApply textures and other material settings aswell
    shader_program.UnApply ();

    glDisable (GL_COLOR_MATERIAL);

    glDisable (GL_TEXTURE_2D);
    glDisable (GL_BLEND);
}

void G3::MATERIAL::dump () {
    if (log) {
        log->Report ("Name: \"%s\"\n", name.c_str ());
        log->Report ("Ambient: (%f, %f, %f, %f)\n", ambient.r, ambient.g, ambient.b, ambient.a);
        log->Report ("Diffuse: (%f, %f, %f, %f)\n", diffuse.r, diffuse.g, diffuse.b, diffuse.a);
        log->Report ("Specular: (%f, %f, %f, %f)\n", specular.r, specular.g, specular.b, specular.a);
        log->Report ("Emission: (%f, %f, %f, %f)\n", emission.r, emission.g, emission.b, emission.a);
        log->Report ("Shininess: %f\n", shininess);
        log->Report ("Reflectance: %f\n", reflectance);
    }
}
